package biolabs.space;

/**
 * {@code Grid} represents an orthogonal multi-dimensional array.
 *
 * @author Tomas Mikula
 */
public abstract class Grid<T, P extends DiscretePosition<P,C>, C extends ContinuousPosition<C,P>> {

	/**
	 * Returns the dimensions of this grid.
	 */
	public abstract P getSize();

	/**
	 * Returns {@code true} if {@code pos} is within the range of this grid.
	 */
	public abstract boolean inRange(P pos);

	/**
	 * Returns the element at position {@code pos}.
	 * @throws ArrayIndexOutOfBoundsException if {@code pos} is out of range
	 */
	public abstract T at(P pos);

	/**
	 * Sets value at position {@code pos} to {@code value}.
	 * Optional operation.
	 * @return value previously stored at {@code pos}.
	 * @throws IndexOutOfBoundsException if {@code pos} is out of range.
	 * @throws UnsupportedOperationException if the implementation
	 *   does not support modification.
	 */
	public T setField(P pos, T value){
		if(!inRange(pos))
			throw new ArrayIndexOutOfBoundsException(pos + " is out of " + getSize());
		return _setField(pos, value);
	}

	/**
	 * As {@link #setField(DiscretePosition, Object)}, but does not do range checking.
	 */
	protected abstract T _setField(P pos, T value);

	/**
	 * Fills the grid with the given object. Each position
	 * in the grid will hold the same instance.
	 */
	public abstract void fill(T value);

	/**
	 * Returns a copy of a portion of this grid.
	 * The origin of the copy will be mapped to the origin of the coordinate
	 * system, i.e. valid indices to the returned grid are between <b>0</b>
	 * and {@code size}-<b>1</b> (inclusive), where <b>0</b> and <b>1</b>
	 * are vectors of zeros and ones, respectively.
	 * @param origin origin of the copied area
	 * @param size size of the copied area
	 * @return a copy of the rectangular area between {@code origin} and
	 *   {@code size}-<b>1</b> (inclusive).
	 * @throws IndexOutOfBoundsException if {@code origin} or
	 *   {@code origin+size}-<b>1</b> is out of this grid's range.
	 * @throws IllegalArgumentException if {@code size} contains
	 *   a non-positive coordinate.
	 */
	public abstract Grid<T, P, C> copyOf(P origin, P size);

	/**
	 * Copies the area between {@code pos} and {@code pos+size}-<b>1</b>
	 * (inclusive) to grid {@code tgt} at position {@code tgtPos}.
	 * @param tgt
	 * @param tgtPos
	 * @param pos
	 * @param size
	 * @throws IndexOutOfBoundsException if {@code pos} or
	 *   {@code pos+size}-<b>1</b> is out of this grid's range, or if
	 *   {@code tgtPos} or {@code tgtPos+size}-<b>1</b> is out of
	 *   {@code tgt}'s range.
	 * @throws IllegalArgumentException if {@code size} contains
	 *   a non-positive coordinate.
	 */
	public void copyTo(Grid<T, P, C> tgt, P tgtPos, P pos, P size){
		for(int i=0, dim=size.getDimension(); i<dim; ++i)
			if(size.getCoordinate(i) <= 0)
				throw new IllegalArgumentException(size + " has a non-positive coordinate");
		if(!inRange(pos) || !inRange(pos.plus(size).minus(size.one())))
			throw new IndexOutOfBoundsException("The requested frame is out of range");
		if(!tgt.inRange(tgtPos) || !tgt.inRange(tgtPos.plus(size).minus(size.one())))
			throw new IndexOutOfBoundsException("The frame does not fit into the target grid");
		_copyTo(tgt, tgtPos, pos, size);
	}

	/**
	 * As {@link #copyTo(Grid, DiscretePosition, DiscretePosition, DiscretePosition)},
	 * but does not do range checking.
	 */
	protected void _copyTo(Grid<T, P, C> tgt, P tgtPos, P pos, P size){
		_copyTo(0, tgt, tgtPos, pos, size);
	}

	private void _copyTo(int nFixedCoords, Grid<T, P, C> tgt, P tgtPos, P srcPos, P size) {
		// TODO could use some parallelism
		if(nFixedCoords == size.getDimension())
			tgt._setField(tgtPos, this.at(srcPos));
		else {
			int n = size.getCoordinate(nFixedCoords);
			int srcCoord = srcPos.getCoordinate(nFixedCoords);
			int tgtCoord = tgtPos.getCoordinate(nFixedCoords);
			for(int i=0; i<n; ++i){
				srcPos = srcPos.withChangedCoordinate(nFixedCoords, srcCoord+i);
				tgtPos = tgtPos.withChangedCoordinate(nFixedCoords, tgtCoord+i);
				_copyTo(nFixedCoords+1, tgt, tgtPos, srcPos, size);
			}
		}
	}
}